package Semant;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;


import Translate.*;
import Types.*;

public class Semant {
	Env env;
	public Semant(ErrorMsg.ErrorMsg err) {this(new Env(err));}
	Semant(Env e) {env=e;}
	
	Type INT = new INT();
	Type STRING = new STRING();
	Type NIL = new NIL();
	Type VOID = new VOID();
	int LOOPNUM = 0;
	
	//checkInt
	private Translate.Exp checkInt(ExpTy et, int pos)
	{
		if ( !(et.ty instanceof Types.INT) )
		{
			error(pos, "Integer required.");
			return null;
		}
		return et.exp;
	}
	
	//tranProg
	public void transProg(Absyn.Exp exp){
		transExp(exp);
	}
	
	//transExp
	ExpTy transExp(Absyn.Exp e) {
		if (e instanceof Absyn.StringExp)
			return transExp((Absyn.StringExp)e);
		if (e instanceof Absyn.IntExp)
			return transExp((Absyn.IntExp)e);
		if (e instanceof Absyn.NilExp)
			return transExp((Absyn.NilExp)e);
		if (e instanceof Absyn.VarExp)
			return transExp((Absyn.VarExp)e);
		if (e instanceof Absyn.OpExp)
			return transExp((Absyn.OpExp)e);
		if (e instanceof Absyn.IfExp)
			return transExp((Absyn.IfExp)e);
		if (e instanceof Absyn.AssignExp)
			return transExp((Absyn.AssignExp)e);
		if (e instanceof Absyn.CallExp)
			return transExp((Absyn.CallExp)e);
		if (e instanceof Absyn.SeqExp)
			return transExp((Absyn.SeqExp)e);
		if (e instanceof Absyn.RecordExp)
			return transExp((Absyn.RecordExp)e);
		if (e instanceof Absyn.ArrayExp) 
			return transExp((Absyn.ArrayExp)e);
		if (e instanceof Absyn.WhileExp)
			return transExp((Absyn.WhileExp)e);
		if (e instanceof Absyn.ForExp)
			return transExp((Absyn.ForExp)e);
		if (e instanceof Absyn.BreakExp) 
			return transExp((Absyn.BreakExp)e);
		if (e instanceof Absyn.LetExp)
			return transExp((Absyn.LetExp)e);
		
		throw new Error("transExp");			
	}
	
	ExpTy transExp(Absyn.StringExp e) {
		return new ExpTy(null, STRING);
	}
	ExpTy transExp(Absyn.IntExp e) {
		return new ExpTy(null, INT);
	}
	ExpTy transExp(Absyn.NilExp e) {
		return new ExpTy(null, NIL);
	}
	ExpTy transExp(Absyn.VarExp e) {
		return transVar(e.var);
	}
	ExpTy transExp(Absyn.OpExp e) {
		ExpTy left = transExp(e.left);
		ExpTy right = transExp(e.right);
		switch (e.oper){
		case Absyn.OpExp.PLUS:
		case Absyn.OpExp.MINUS:
		case Absyn.OpExp.MUL:
		case Absyn.OpExp.DIV:
			checkInt(left,e.left.pos);
			checkInt(right,e.right.pos);
			return new ExpTy(null,INT);
		case Absyn.OpExp.GT:
		case Absyn.OpExp.LT:
		case Absyn.OpExp.GE:
		case Absyn.OpExp.LE:
			if (left.ty.coerceTo(INT) && right.ty.coerceTo(INT))
				return new ExpTy(null,INT);
			if (left.ty.coerceTo(STRING) && right.ty.coerceTo(STRING))
				return new ExpTy(null,INT);
			env.errorMsg.error(e.left.pos, "INT or STRING required or not match each other in OpExp");
			return new ExpTy(null,INT);
		case Absyn.OpExp.EQ:
		case Absyn.OpExp.NE:
			if (left.ty.coerceTo(INT) && right.ty.coerceTo(INT))
				return new ExpTy(null,INT);
			if (left.ty.coerceTo(STRING) && right.ty.coerceTo(STRING))
				return new ExpTy(null,INT);
			if (left.ty.coerceTo(right.ty) && right.ty.actual() instanceof ARRAY)
				return new ExpTy(null,INT);
			if (left.ty.coerceTo(right.ty) && right.ty.actual() instanceof RECORD)
				return new ExpTy(null,INT);
			if (left.ty.coerceTo(NIL) && right.ty.actual() instanceof RECORD)
				return new ExpTy(null,INT);
			if (left.ty.actual() instanceof RECORD && right.ty.coerceTo(NIL))
				return new ExpTy(null,INT);
			env.errorMsg.error(e.left.pos, "Type ERROR! in OpExp of EQ and NE");
			return new ExpTy(null,INT);
		default:
			env.errorMsg.error(e.left.pos, "Oper ERROR!");
			return new ExpTy(null,INT);
		}

	}
	ExpTy transExp(Absyn.IfExp e) {
		ExpTy test = transExp(e.test);
		if (!(test.ty instanceof INT)){
			error(e.pos,"test must be INT");
		}
		
		if (e.elseclause == null){
			ExpTy thenclause = transExp(e.thenclause);
			if (!(thenclause.ty.coerceTo(VOID)))
				error(e.pos,"then should return VOID");
			return new ExpTy(null,VOID);
		}
		else{
			ExpTy thenclause = transExp(e.thenclause);
			ExpTy elseclause = transExp(e.elseclause);
			if (thenclause.ty.coerceTo(VOID) && elseclause.ty.coerceTo(VOID))
				return new ExpTy(null,VOID);
			if (thenclause.ty.coerceTo(INT) && elseclause.ty.coerceTo(INT))
				return new ExpTy(null,INT);
			if (thenclause.ty.coerceTo(STRING) && elseclause.ty.coerceTo(STRING))
				return new ExpTy(null,STRING);
			if (thenclause.ty.coerceTo(elseclause.ty) && elseclause.ty.actual() instanceof ARRAY)
				return new ExpTy(null,elseclause.ty.actual());
			if (thenclause.ty.coerceTo(elseclause.ty) && elseclause.ty.actual() instanceof RECORD)
				return new ExpTy(null,elseclause.ty.actual());
			if (thenclause.ty.coerceTo(NIL) && elseclause.ty.actual() instanceof RECORD)
				return new ExpTy(null,elseclause.ty.actual());
			if (thenclause.ty.actual() instanceof RECORD && elseclause.ty.coerceTo(NIL))
				return new ExpTy(null,thenclause.ty.actual());
		}
		error(e.pos,"this massage should not be diaplayed in ifExp, thenclause and elseclause type do not match");
		return new ExpTy(null, VOID);
	}
	
	ExpTy transExp(Absyn.AssignExp e) {
		ExpTy left = transVar(e.var);
		ExpTy right = transExp(e.exp);
		if (e.var instanceof Absyn.SimpleVar && env.venv.get(((Absyn.SimpleVar)e.var).name) instanceof LoopVarEntry)
			error(e.pos, "Loop value should not be assigned");
		if (!(right.ty.coerceTo(left.ty)))
			error(e.pos,"wrong assign value");
		return new ExpTy(null, VOID);
	}

	ExpTy transExp(Absyn.CallExp e) {
		Entry func = (Entry) env.venv.get(e.func);
		if ( func == null || !(func instanceof FunEntry) )
		{
			error(e.pos, "Undeclared function: " + e.func);
			return new ExpTy(null, VOID);
		}
		Absyn.ExpList eArgument = e.args;
		RECORD eFormals = ((FunEntry)func).formals;
		ExpTy tmp = null;
		
		for (; eArgument != null; eArgument = eArgument.tail, eFormals = eFormals.tail) {
			if (eFormals == null) {
				error(e.pos, "Function " + e.func.toString() + " has too many arguments");
				break;
			}
			tmp = transExp(eArgument.head);
			if (!(tmp.ty.coerceTo(eFormals.fieldType)))
					error(e.pos,"args error");

		}
		if (eFormals != null)
			error(e.pos, "Function " + e.func.toString() + "'s arguments are lesser then expected");
		return new ExpTy(null, ((FunEntry)func).result.actual());

	}
	
	ExpTy transExp(Absyn.SeqExp e) {
		Type type = new VOID();

		for ( Absyn.ExpList list = e.list; list != null; list = list.tail )
		{
			ExpTy et = transExp(list.head);
			type = et.ty;

		}
		return new ExpTy(null, type.actual());
	}
	
	ExpTy transExp(Absyn.RecordExp e) {
		Type type = (Type)env.tenv.get(e.typ);
		if (type == null) {
			error(e.pos, "Undefined record type " + e.typ.toString());
			return new ExpTy(null, INT);
		}
		
		type = type.actual();
		if (!(type instanceof RECORD)) {
			error(e.pos, "Record type required");
			return new ExpTy(null, INT);
		}

		Absyn.FieldExpList fields = e.fields;
		RECORD record = (RECORD)type;
		ExpTy et;

		ArrayList <Exp> fieldList = new ArrayList<Exp> ();
		for (; fields != null; fields = fields.tail, record = record.tail) {
			if (record == null) {
				error(fields.pos,"Field " + fields.name.toString() + " has not been declared");
				break;
			}
			if (record.fieldName != fields.name) {
				error(fields.pos, record.fieldName.toString() + " field dismatch");
				break;
			}
			et = transExp(fields.init);
			fieldList.add(et.exp);
			if (!(et.ty.coerceTo(record.fieldType.actual())))
				 error(fields.pos,"type do not match in the record field");
			
		}

		if (record != null)
			error(fields.pos, "Missing record fields");
		return new ExpTy(null, type);
	}
	
	ExpTy transExp(Absyn.ArrayExp e) {
		Type type = (Type)env.tenv.get(e.typ);
		if (type == null) {
			error(e.pos, "Undefined array type");
			return new ExpTy(null, INT);
		}
		
		type = type.actual();
		if (!(type instanceof ARRAY)) {
			error(e.pos, "Array type required");
			return new ExpTy(null, INT);
		}
		
		ExpTy arraySize = transExp(e.size);
		checkInt(arraySize, e.size.pos);
		
		ExpTy init = transExp(e.init);
		if (!(init.ty.coerceTo(((ARRAY)type).element.actual())))
			 error(e.pos,"type do not match of the array");
		
		if (!init.ty.actual().coerceTo(INT)) {
			return new ExpTy(null, type);
		}
		else
			return new ExpTy(null, type);
	}
	
	ExpTy transExp(Absyn.WhileExp e) {
		ExpTy test = transExp(e.test);
		checkInt(test, e.test.pos);
		//LoopSemant ls = new LoopSemant(env);
		LOOPNUM++;
		ExpTy body = transExp(e.body);
		if ( !(body.ty.actual() instanceof VOID) )
			error(e.body.pos, "Body of WHILE cannot return any value.");
		LOOPNUM--;
		return new ExpTy(null,VOID);
	}

	
	ExpTy transExp(Absyn.ForExp e) {
		ExpTy low = transExp(e.var.init);
		checkInt(low, e.var.pos);
		ExpTy high = transExp(e.hi);
		checkInt(high, e.hi.pos);
		env.venv.beginScope();
		//Access access = level.allocLocal(e.var.escape);
		VarEntry entry = new LoopVarEntry(INT);
		env.venv.put(e.var.name, entry);
		LOOPNUM++;
		//LoopSemant ls = new LoopSemant(env);
		ExpTy body = transExp(e.body);
		env.venv.endScope();
		if ( !(body.ty.actual() instanceof VOID) )
		{
			error(e.body.pos, "Body of FOR cannot return any value.");
		}
		LOOPNUM--;
		return new ExpTy(null,VOID);
	}
	
	ExpTy transExp(Absyn.BreakExp e) {
		if (LOOPNUM <= 0)
			error(e.pos, "Break must be in loop.");
		return new ExpTy(null,VOID);
	}
	
	ExpTy transExp(Absyn.LetExp e)
	{
		env.venv.beginScope();
		env.tenv.beginScope();
		//Translate.ExpList el = new Translate.ExpList(null, null), el1 = el;
		for ( Absyn.DecList p = e.decs; p != null; p = p.tail )
		{
			//el1 = el1.tail = new Translate.ExpList(transDec(p.head), null);
			transDec(p.head);
		}
		ExpTy et = transExp(e.body);
		env.tenv.endScope();
		env.venv.endScope();
		return new ExpTy(null, et.ty.actual());
	}
	
	
	//transVarExp
	ExpTy transVar(Absyn.Var v)
	{
		if (v instanceof Absyn.SimpleVar)
			return transVar((Absyn.SimpleVar)v);
		if (v instanceof Absyn.SubscriptVar)
			return transVar((Absyn.SubscriptVar)v);
		if (v instanceof Absyn.FieldVar)
			return transVar((Absyn.FieldVar)v);
		error(v.pos,"Unknow VarExp");
		return new ExpTy(null,INT);
	}
	
	ExpTy transVar(Absyn.SimpleVar v){
		Entry x = (Entry)env.venv.get(v.name);
		//if (x instanceof LoopVarEntry){
			//error(v.pos,"loop index can't be left value");
			//return new ExpTy(null,VOID);
		//}
		if (x instanceof VarEntry){
			VarEntry ent = (VarEntry)x;
			return new ExpTy(null,ent.ty);
		}
		else{
			error(v.pos,"undefined variable");
			return new ExpTy(null,INT);
		}
	}
	
	ExpTy transVar(Absyn.SubscriptVar v){
		ExpTy array = transVar(v.var);
		if (!(array.ty.actual() instanceof ARRAY)){
			error(v.pos,"ARRAY needed");
			return new ExpTy(null,VOID);
		}
		ExpTy index = transExp(v.index);
		checkInt(index,v.pos);
		return new ExpTy(null,((ARRAY)array.ty).element.actual());
	}

	ExpTy transVar(Absyn.FieldVar v){
		ExpTy l = transVar(v.var);
		int offset = 0;
		if (l.ty instanceof RECORD) {
			RECORD rec;
			for (rec = (RECORD)l.ty; rec != null; rec = rec.tail, offset++)
				if (rec.fieldName.toString() == v.field.toString())
					break;
			if (rec == null) {
				error(v.pos, "Field " + v.field.toString() + " does not exist");
				return new ExpTy(null, INT);
			}
			else
				return new ExpTy(null,rec.fieldType.actual());
		}
		else {
			error(v.pos, "Record type required");
			return new ExpTy(null, INT);
		}
	}
	
	
	
	//transDec

	Exp transDec(Absyn.Dec d){
		//System.out.println("xXX");
		if (d instanceof Absyn.TypeDec)
			return transDec((Absyn.TypeDec)d);
		if (d instanceof Absyn.VarDec)
			return transDec((Absyn.VarDec)d);
		if (d instanceof Absyn.FunctionDec)
			return transDec((Absyn.FunctionDec)d);
		error(d.pos,"unknow dec");
		return null;
	}

	Exp transDec(Absyn.TypeDec d) {
		List <Symbol.Symbol> list = new ArrayList <Symbol.Symbol> ();
		for (Absyn.TypeDec now = d; now != null; now = now.next)
			if (list.contains(now.name))
				error(now.pos, "This type has been defined in this type declaration sequence");
			else {
				list.add(now.name);
				env.tenv.put(now.name,new NAME(now.name));
			}
		for (Absyn.TypeDec now = d; now != null; now = now.next)
			((NAME)env.tenv.get(now.name)).bind(transTy(now.ty));
		for (Absyn.TypeDec now = d; now != null; now = now.next)
			if (((NAME)env.tenv.get(now.name)).isLoop())
				error(now.pos, "This is a loop type declaration");
		return null;
	}
	
	Exp transDec(Absyn.VarDec d){
		ExpTy init = transExp(d.init);
		Type ty = null;
		if (d.typ == null)
			if (init.ty == NIL) {
				error(d.init.pos, "Illegal varible initialization");
				return null;
			} else
				ty =  init.ty;
		else {
			ty = transTy(d.typ).actual();
			if (!(init.ty.coerceTo(ty)))
				error(d.pos,"the init type and the typ type is not match");
		}
		//Access access = level.allocLocal(d.escape);
		env.venv.put(d.name, new VarEntry(ty));
		return null;//translate.transAssignExp(translate.transSimpleVar(access, level), dInit.exp);
	
	}
	

	Exp transDec(Absyn.FunctionDec d){
		//System.out.println("xXX");
		List<Symbol.Symbol> list = new ArrayList <Symbol.Symbol> ();
		for (Absyn.FunctionDec now = d; now != null; now = now.next) {
			if (list.contains(now.name))
				error(now.pos, "This funtion has been defined in this function declaration sequence");
			else {
				list.add(now.name);
				Type result = (now.result == null) ? VOID : transTy(now.result).actual();
				//Label label = new Label("P" + count + "_" + it.name);
				//count++;
				//Level new_level = new Level(level, label, makeBoolList(it.params));
				env.venv.put(now.name, new FunEntry((RECORD)transTypeFields(now.params), result));
			}
		}
		//System.out.println("xXX");
		for (Absyn.FunctionDec now = d; now != null; now = now.next) {
			//if (it.inline) continue;
			FunEntry f = (FunEntry)env.venv.get(now.name);
			env.venv.beginScope();
			
			int LOOPTEMP = LOOPNUM;
			LOOPNUM = 0;
			//Level pLevel = level;
			//level = f.level;
			//AccessList al = level.formals.tail;
			for (Absyn.FieldList p = now.params; p != null; p = p.tail) {
				Type ty = (Type)env.tenv.get(p.typ);
				if (ty == null) {
					error(p.pos, "Undefined type" + p.typ.toString());
					env.venv.endScope();
					return null;
				}
				else {
					//Access acc = new Access(level, al.head.access);
					env.venv.put(p.name, new VarEntry(ty.actual()));
				}
			}
			ExpTy et = transExp(now.body);
			//translate.procEntryExit(level, et.exp, et.ty != VOID);
			LOOPNUM = LOOPTEMP;
			env.venv.endScope();
			//level = pLevel;
			if (!(et.ty.coerceTo(f.result.actual())))
				error(d.pos,"type of result exp is not match the type-id or the result should be VOID");
		}
		return null;
	
	}
	

	
	//transTy
	Type transTy(Absyn.Ty t){
		if ( t instanceof Absyn.NameTy )
			return transTy((Absyn.NameTy) t);
		else if ( t instanceof Absyn.ArrayTy )
			return transTy((Absyn.ArrayTy) t);
		else if ( t instanceof Absyn.RecordTy )
			return transTy((Absyn.RecordTy) t);
		else
			return null;
	}
	
	Type transTy(Absyn.NameTy t){
		Type type = (Type) env.tenv.get(t.name);
		if ( type == null ) error(t.pos, "Unknown type: " + t.name);
		return type;
	}
	
	Type transTy(Absyn.ArrayTy t){
		Type type = (Type) env.tenv.get(t.typ);
		if ( type == null ) error(t.pos, "Unknown type:" + t.typ);
		return new ARRAY(type);
	}
	
	Type transTy(Absyn.RecordTy t){
		java.util.Stack<RECORD> stack = new Stack<RECORD>();
		RECORD result = null;
		for ( Absyn.FieldList fields = t.fields; fields != null; fields = fields.tail )
		{
			Type type = (Type) env.tenv.get(fields.typ);
			if ( type == null )
				error(fields.pos, "Unknown type: " + fields.typ);
			stack.push(new RECORD(fields.name, type, result));
		}
		while ( !stack.empty() )
		{
			RECORD record = stack.pop();
			record.tail = result;
			result = record;
		}
		if (result==null) return new RECORD(Symbol.Symbol.symbol("=.="),VOID,null);
		return result;
	}
	
	Type transTypeFields(Absyn.FieldList p) {
		RECORD result = null, ptr = null;
		for (; p != null; p = p.tail) {
			Type t = ((Type)(env.tenv.get(p.typ))).actual();
			if (t == null)
				error(p.pos, "Undefined type " + p.typ.toString());
			else if (ptr == null)
				result = ptr = new RECORD(p.name, t, null);
			else {
				ptr.tail = new RECORD(p.name, t, null);
				ptr = ptr.tail;
			}
		}

		return result;
	}
	
	//error
	private void error(int pos, String msg)	{
		env.errorMsg.error(pos, msg);
	}

}

/*
class LoopSemant extends Semant
{
	//Temp.Label	done;
	
	LoopSemant(Env env)
	{
		super(env);
		//done = new Temp.Label();
	}
	
	ExpTy transExp(Absyn.BreakExp e)
	{
		return new ExpTy(null, VOID);
	}
}
*/
